home *** CD-ROM | disk | FTP | other *** search
- # -*- test-case-name: epsilon.test.test_amprouter -*-
- # Copyright (c) 2008 Divmod. See LICENSE for details.
-
- """
- This module provides an implementation of I{Routes}, a system for multiplexing
- multiple L{IBoxReceiver}/I{IBoxSender} pairs over a single L{AMP} connection.
- """
-
- from itertools import count
-
- from zope.interface import implements
-
- from twisted.protocols.amp import IBoxReceiver, IBoxSender
-
- from epsilon.structlike import record
-
- __metaclass__ = type
-
- _ROUTE = '_route'
- _unspecified = object()
-
-
- class RouteNotConnected(Exception):
- """
- An attempt was made to send AMP boxes through a L{Route} which is not yet
- connected to anything.
- """
-
-
-
- class Route(record('router receiver localRouteName remoteRouteName',
- remoteRouteName=_unspecified)):
- """
- Wrap up a route name and a box sender to transparently add the route name
- to boxes sent through this box sender.
-
- @ivar router: The L{Router} which created this route. This will be used
- for route tear down and for its L{IBoxSender}, to send boxes.
-
- @ivar receiver: The receiver which will be started with this object as its
- sender.
-
- @type localRouteName: C{unicode}
- @ivar localRouteName: The name of this route as known by the other side of
- the AMP connection. AMP boxes with this route are expected to be
- routed to this object.
-
- @type remoteRouteName: C{unicode} or L{NoneType}
- @ivar remoteRouteName: The name of the route which will be added to all
- boxes sent to this sender. If C{None}, no route will be added.
- """
- implements(IBoxSender)
-
- def connectTo(self, remoteRouteName):
- """
- Set the name of the route which will be added to outgoing boxes.
- """
- self.remoteRouteName = remoteRouteName
- # This route must not be started before its router is started. If
- # sender is None, then the router is not started. When the router is
- # started, it will start this route.
- if self.router._sender is not None:
- self.start()
-
-
- def unbind(self):
- """
- Remove the association between this route and its router.
- """
- del self.router._routes[self.localRouteName]
-
-
- def start(self):
- """
- Associate this object with a receiver as its L{IBoxSender}.
- """
- self.receiver.startReceivingBoxes(self)
-
-
- def stop(self, reason):
- """
- Shut down the underlying receiver.
- """
- self.receiver.stopReceivingBoxes(reason)
-
-
- def sendBox(self, box):
- """
- Add the route and send the box.
- """
- if self.remoteRouteName is _unspecified:
- raise RouteNotConnected()
- if self.remoteRouteName is not None:
- box[_ROUTE] = self.remoteRouteName.encode('ascii')
- self.router._sender.sendBox(box)
-
-
- def unhandledError(self, failure):
- """
- Pass failures through to the wrapped L{IBoxSender} without
- modification.
- """
- self.router._sender.unhandledError(failure)
-
-
-
- class Router:
- """
- An L{IBoxReceiver} implementation which demultiplexes boxes from an AMP
- connection being used with zero, one, or more routes.
-
- @ivar _sender: An L{IBoxSender} provider which is used to allow
- L{IBoxReceiver}s added to this router to send boxes.
-
- @ivar _unstarted: A C{dict} similar to C{_routes} set before
- C{startReceivingBoxes} is called and containing all routes which have
- been added but not yet started. These are started and moved to the
- C{_routes} dict when the router is started.
-
- @ivar _routes: A C{dict} mapping local route identifiers to
- L{IBoxReceivers} associated with them. This is only initialized after
- C{startReceivingBoxes} is called.
-
- @ivar _routeCounter: A L{itertools.count} instance used to generate unique
- identifiers for routes in this router.
- """
- implements(IBoxReceiver)
-
- _routes = None
- _sender = None
-
- def __init__(self):
- self._routeCounter = count()
- self._unstarted = {}
-
-
- def createRouteIdentifier(self):
- """
- Return a route identifier which is not yet associated with a route on
- this dispatcher.
-
- @rtype: C{unicode}
- """
- return unicode(self._routeCounter.next())
-
-
- def bindRoute(self, receiver, routeName=_unspecified):
- """
- Create a new route to associate the given route name with the given
- receiver.
-
- @type routeName: C{unicode} or L{NoneType}
- @param routeName: The identifier for the newly created route. If
- C{None}, boxes with no route in them will be delivered to this
- receiver.
-
- @rtype: L{Route}
- """
- if routeName is _unspecified:
- routeName = self.createRouteIdentifier()
- # self._sender may yet be None; if so, this route goes into _unstarted
- # and will have its sender set correctly in startReceivingBoxes below.
- route = Route(self, receiver, routeName)
- mapping = self._routes
- if mapping is None:
- mapping = self._unstarted
- mapping[routeName] = route
- return route
-
-
- def startReceivingBoxes(self, sender):
- """
- Initialize route tracking objects.
- """
- self._sender = sender
- for routeName, route in self._unstarted.iteritems():
- # Any route which has been bound but which does not yet have a
- # remote route name should not yet be started. These will be
- # started in Route.connectTo.
- if route.remoteRouteName is not _unspecified:
- route.start()
- self._routes = self._unstarted
- self._unstarted = None
-
-
- def ampBoxReceived(self, box):
- """
- Dispatch the given box to the L{IBoxReceiver} associated with the route
- indicated by the box, or handle it directly if there is no route.
- """
- route = box.pop(_ROUTE, None)
- self._routes[route].receiver.ampBoxReceived(box)
-
-
- def stopReceivingBoxes(self, reason):
- """
- Stop all the L{IBoxReceiver}s which have been added to this router.
- """
- for routeName, route in self._routes.iteritems():
- route.stop(reason)
- self._routes = None
-
-
-
- __all__ = ['Router', 'Route']
-